#include <stdint.h>
#include <stdlib.h>
#include "../Scanning/Scanning.h"
#include "../Context/ExeContext.h"
#include "../Context/ExeContextRepository.h"
#include "../BMC.h"
#include "../BMCRepository.h"
#include "../../6502.h"
#include "../../JRYNES.h"
#include "../../6502_Debug.h"

#include "EE.h"

extern EmulationSettings Settings;

extern __Context CPU;

ECTXTEntry* ECTXTRepository;

void EE_Init(void){
    ECTXTRepository = ECTXTRNew();
}

#define UPDATE_CLOCK_TICKS \
        CPU.ClockTicks += PrevCycles - RemainingCycles; \
        CPU.ClockTicks += CPU.InstructionClockTicks; \
        PrevCycles = RemainingCycles;

int32_t EE_Main(int32_t Cycles){
    
    int32_t PrevCycles, RemainingCycles;
    BMC* Block;
    uint32_t DoInt = 0;
    
    ExeContext *NewContext;
    ExeContext *Context;
    
    RemainingCycles = Cycles;
    PrevCycles = Cycles;
    
    while(RemainingCycles > 0){     
        
        CPU.InstructionClockTicks = 0;
        
        if(CPU.DMACycles){
            if(RemainingCycles <= CPU.DMACycles){
                //burn remaining cycles and end cycle execution
                CPU.DMACycles -= RemainingCycles;
                CPU.ClockTicks += RemainingCycles;
                break;
            }
            else{
                RemainingCycles -= CPU.DMACycles;
                CPU.ClockTicks += CPU.DMACycles;
                CPU.DMACycles = 0;
            }                
        }
        
        if(CPU.PendingInterrupts){
            if(CPU.PendingInterrupts & INT_NMI){
                NMI();
            }

            if(CPU.PendingInterrupts & INT_IRQ){
                if(!STATUS_INTERRUPT){
                    IRQ();
                }
            }
            RemainingCycles -= 7;
        }        
        
        #ifdef DEBUGCONSOLE
            debugCycle(); 
        #endif         
        
        Block = BMCRSearch(CPU.PC);
        
        if(!Block){
            SNN_InstructionFault(CPU.PC);
            
            if(Settings.ArgumentFlags & AF_DBT_PARALLEL_COMPILATION)
                DoInt = 1;
        }
        else{
            if(Block->CompleteECTXTUnoptimized){
                
                if(Block->StartAddr == CPU.PC){
                    if((Settings.ArgumentFlags & AF_CTXT_INFO) && (Block->ContextsOptimized < Settings.MaxContexts)){
                    
                        NewContext = ECX_NewFromCPUContext(&CPU, ECTXT_MASK_PC | Settings.ContextMask);

                        Context = ECTXTRSearch(ECTXTRepository, NewContext);

                        if(!Context){
                            ECTXTRInsert(ECTXTRepository, NewContext);
                        }
                        else{
                            free(NewContext);

                            ECX_IncrementExecutionCount(Context);

                            if(Context->Code){
                                RemainingCycles = Context->Code->BlockCode(RemainingCycles);
                                UPDATE_CLOCK_TICKS
                                continue;
                            }
                            else{
                                if(Context->ExecutionCount > Settings.ContextThreshold){
                                    //Translate for the current context
                                    //char* ctxt_string;
                                    //ctxt_string = ECX_GenerateContextString(Context);
                                    //printf("Need to translate 0x%04X for%s (%ld)\n", Block->StartAddr, ctxt_string, Context->ExecutionCount);
                                    TRL_OptimizationRequest(Context);
                                    Context->SourceBMC->ContextsOptimized++;
                                    //free(ctxt_string);
                                }
                            }
                        }    
                    }
                }
                
                RemainingCycles = Block->CompleteECTXTUnoptimized->BlockCode(RemainingCycles);
                UPDATE_CLOCK_TICKS
                        
            }
            else{
                    DoInt = 1;
            }
            
        }
        
        if(DoInt){
            //Cycles are updated inside the interpreter
            //Thus, no explicit cycle update is needed
            //after INT_runCycles
            return INT_runCycles(RemainingCycles);
            break;
        }
        
    }
    
    //TODO: Fix clock cycle counter
    //CPU.ClockTicks += Cycles;
    
    //"VPC"
    
    //IF block->puro THEN
    //  ptr = HMC(END, CONTEXTO)
    //END IF
    
    //IF(!ptr) THEN
    //  ptr = HMC(END, GENERICO)
    //END IF
    
    //RETURN ptr();
    
    //versão 0.1
    //while(RemainingCycles > 0){
        //VPC = InstructionMap[Guest.PC]
        //if(VPC.Invalid){
            //No HMC has been generated for this VPC address
            //SNN_InstructionFault(PC);
        //}
        //else{
            //Execute the HMC corresponding to current context
            
            //!!!!!It is important to make sure that we only run optimized code
            //when the target instruction is the first instruction in the block!!!!!
            
            //RemainingCycles = VPC(RemainingCycles);
        //}
    //}
    
    return RemainingCycles;
}
